#! /usr/bin/env python

from datetime import datetime
from os import path
import os
import zipfile
from util import Bunch

from model import db, Plan, Log, Client
import pxe
import cfg

pxe_cfg = pxe.Pxe()


# @db.execution_context()
def create(mac, pxe_list, time=None, rack=None, location=None, sn=None):
    mac = mac.lower()
    if not time:
        time = datetime.now()
    client = Client.select().where(Client.mac == mac).first()
    if not client:
        client = Client.create(mac=mac)

    if rack:
        client.rack = rack
    if location:
        client.location = location
    if sn:
        client.sn = sn
    if rack or location or sn:
        print 'update client'
        client.save()
    delete_plan_for_client(client)
    if len(pxe_list) < 1:
        return None
    order = 1
    for pxe, cycle in pxe_list:
        Plan.create(
            client=client,
            pxe=pxe.replace('\\', '/'),
            cycle=cycle,
            order=order,
            time=time)
        order += 1

    pxe_cfg.new_pxe(mac, pxe_list[0][0])


# @db.execution_context()
def list(mac_filter=None):
    # plan = Plan.select(Plan.client).order_by(+Plan.client.rack, +Client.location, +Plan.client.sn, +Plan.client.mac).distinct()
    if mac_filter:
        plan = Plan.select(
            Plan.client).join(Client).where(Client.mac << mac_filter).order_by(
                +Client.rack, +Client.location, +Client.mac).distinct()
    else:
        plan = Plan.select(Plan.client).join(Client).order_by(
            +Client.rack, +Client.location, +Client.mac).distinct()
    return [read(i.client.mac) for i in plan]


# @db.execution_context()
def read(mac):
    mac = mac.lower()
    client = Client.select().where(Client.mac == mac).first()
    if not client:
        return None
    plan = Plan.select().where(Plan.client == client).order_by(+Plan.order)
    pxe_list = []
    progress = dict(count=0, cycle=0)
    for p in plan:
        query = Log.select().where(Log.client == client, Log.pxe == p.pxe,
                                   Log.time >= p.time).order_by(+Log.time)
        log = [
            Bunch(
                pxe=cyc.pxe,
                # time=cyc.time.strftime('%Y/%m/%d %H:%M:%S'),
                time=cyc.time,
                count=cyc.count) for cyc in query
        ]
        pxe = Bunch(
            pxe=p.pxe,
            order=p.order,
            cycle=p.cycle,
            count=p.count,
            time=p.time,
            done=(True if p.count == p.cycle else False),
            log=log)
        progress['count'] += p.count
        progress['cycle'] += p.cycle
        pxe_list.append(pxe)
    if pxe_list == []:
        return None
    finished = check_finished(mac)
    progress = Bunch(
        text='%d/%d' % (progress['count'], progress['cycle']),
        value=int(100.0 * progress['count'] / progress['cycle']))
    return Bunch(
        client=client, finished=finished, pxe_list=pxe_list, progress=progress)


# @db.execution_context()
def update(mac, pxe_list):
    pass


# @db.execution_context()
def delete(mac):
    mac = mac.lower()
    client = Client.select().where(Client.mac == mac).first()
    if not client:
        return None
    delete_plan_for_client(client)


# @db.execution_context()
def delete_plan_for_client(client):
    old_plan = (Plan.select().where(Plan.client == client))
    for p in old_plan:
        # print 'delete plan', p.pxe
        p.delete_instance()
    pxe_cfg.clear_pxe(client.mac)


# @db.execution_context()
def ack(mac, interval=0, ts=None, debug=False):
    '''
    report the client with mac has boot to PXE
    mac: client mac
    interval: in seconds. it's a valid ack only if last log is older than interval
    '''
    if not ts:
        ts = datetime.now()
    mac = mac.lower()
    client = Client.select().where(Client.mac == mac).first()
    if not client:
        return None
    plan = Plan.select().where(
        Plan.client == client,
        Plan.count < Plan.cycle).order_by(+Plan.order).first()
    if not plan:
        # no plan or all done
        return None
    last_log = Log.select().where(
        Log.client == client).order_by(-Log.time).first()

    if last_log:
        dt = (ts - last_log.time).total_seconds()
        if dt > 0 and dt <= interval:
            # last log is NOT older than interval
            # sometimes dt can be negative value, don't know why
            return None
    plan.count += 1
    plan.save()
    # print 'ack plan count', plan.count
    # c = Client.select().where(Client.mac=='08:00:27:df:0e:00')
    # print 'ack plan count2', Plan.select().where(Plan.client==client, Plan.order==1).first().count
    if plan.count == plan.cycle:
        plan.done = True
        plan.save()
    # print plan.pxe, plan.count
    plan_next = Plan.select().where(
        Plan.client == client,
        Plan.count < Plan.cycle).order_by(+Plan.order).first()
    if debug:
        # do not write pxe_cfg file if debug
        # print 'debug'
        pass
    else:
        if plan_next:
            pxe_cfg.new_pxe(mac, plan_next.pxe)
        else:
            print 'clear pxe for mac', mac
            pxe_cfg.clear_pxe(mac)
    return Log.create(client=client, time=ts, count=plan.count, pxe=plan.pxe)


# @db.execution_context()
def check_finished(mac):
    mac = mac.lower()
    client = Client.select().where(Client.mac == mac).first()
    if not client:
        return None
    plan = Plan.select().where(Plan.client == client).first()
    if not plan:
        return None
    plan = Plan.select().where(Plan.client == client,
                               Plan.done == False).first()
    if plan:
        # still have unfinished plan
        return False
    else:
        return True


# @db.execution_context()
def get_last_log(mac):
    mac = mac.lower()
    log = Log.select().where(Log.mac == mac).order_by(-Log.time).first()
    return log


# @db.execution_context()
def get_all_log(mac):
    mac = mac.lower()
    client = Client.select().where(Client.mac == mac).first()
    if not client:
        return None
    log = Log.select().where(Log.client == client).order_by(-Log.time)
    return log


def zip_logs():
    log_ext = '.csv'
    zip_logs_path = path.normpath(
        path.join(cfg.log_path, 'pxe-logs-%s.zip' %
                  (datetime.now().strftime('%Y-%m%d-%H%M%S'))))
    if path.exists(zip_logs_path):
        os.unlink(zip_logs_path)
    client_list = Client.select()
    for client in client_list:
        plan = Plan.select().where(Plan.client == client).order_by(+Plan.order)
        # log = Log.select().where(Log.client == client).order_by(-Log.time)
        for p in plan:
            query = Log.select().where(Log.client == client, Log.pxe == p.pxe,
                                       Log.time >= p.time).order_by(+Log.time)
            file_name = '%s-%s-%s-%s-%04d-%04d%s' % (
                p.client.rack, p.client.location,
                p.client.mac.replace(':', ''), p.pxe.replace('/', '_'),
                p.count, p.cycle, log_ext)
            file_path = path.normpath(path.join(cfg.log_path, file_name))
            if not path.exists(file_path):
                log_list = []
                last_ts = None
                for cyc in query:
                    if not last_ts:
                        last_ts = cyc.time
                    log = [
                        cyc.count,
                        int((cyc.time - last_ts).total_seconds()),
                        cyc.time.strftime('%Y/%m/%d-%H:%M:%S')
                    ]
                    log_list.append(','.join([str(i) for i in log]))
                    last_ts = cyc.time
                log_list = '\n'.join(log_list)
                with open(file_path, 'w') as f:
                    f.write(log_list)
            with zipfile.ZipFile(zip_logs_path, 'a') as myzip:
                myzip.write(file_path, 'logs/%s' % (file_name))

    return zip_logs_path


if __name__ == '__main__':
    pass
